package main

import (
	"encoding/json"
	"os"
	"path/filepath"
	"slices"
	"strings"
	"sync"

	"github.com/spf13/cobra"
)

type DaoOptions struct {
	common   CommonOptions
	pkgName  string
	dump     bool
	template string
	output   string
	imports  []string
}

type DaoDocument struct {
	PkgName   string
	Imports   []string
	DaoName   string
	ModelName string
	Table     *TableDesc
	Vars      map[string]any
}

var (
	daoConfig *DaoConfig
)

var initDaoConfig = sync.OnceFunc(func() {
	initCommonConfig()
	daoConfig = getAppConfig().Dao
})

func getDaoTemplate(opts *DaoOptions) string {
	if opts.template != "" {
		return opts.template
	}
	var templateName string
	if getAppConfig().Dao != nil {
		templateName = getAppConfig().Dao.TemplateName
	}
	if templateName == "" {
		templateName = "gorm_dao"
	}
	return templateName
}

func generateDao(database string, tableName string, opts *DaoOptions) error {
	imports := make(map[string]bool)
	for _, importPath := range opts.imports {
		imports[importPath] = true
	}
	for _, path := range daoConfig.Imports {
		imports[path] = true
	}

	baseName := camelCase(removePrefix(tablePrefixes, tableName), false)

	daoName := strings.Replace(daoConfig.DaoName, "{base}", baseName, 1)
	modelType := parseGoType(strings.Replace(daoConfig.ModelName, "{base}", baseName, 1))
	if daoConfig.ImportModel && modelType.PkgPath != "" {
		imports[modelType.PkgPath] = true
	}

	tableDesc, err := getTableDesc(database, tableName, false)
	if err != nil {
		return err
	}

	var importList []string
	for k := range imports {
		importList = append(importList, k)
	}
	slices.Sort(importList)

	doc := &DaoDocument{
		PkgName:   opts.pkgName,
		Imports:   importList,
		ModelName: modelType.Name,
		Vars:      daoConfig.Vars,
		DaoName:   daoName,
		Table:     tableDesc,
	}

	if opts.dump {
		enc := json.NewEncoder(os.Stdout)
		enc.SetEscapeHTML(false)
		enc.SetIndent("", "  ")
		return enc.Encode(doc)
	}

	outputFile := opts.output
	if outputFile == "" {
		outputFile = tableName + ".go"
	}
	return writeOutput(outputFile, filepath.ToSlash(getDaoTemplate(opts)), doc, &opts.common)
}

func daoCommand() *cobra.Command {
	opts := &DaoOptions{}
	cmd := &cobra.Command{
		Use:     "dao [flags] <table>...",
		Short:   "Generate data access code",
		Args:    cobra.MinimumNArgs(1),
		Aliases: []string{"d", "repo"},
		PreRun: func(cmd *cobra.Command, args []string) {
			initDaoConfig()

			if opts.pkgName == "" {
				wd, _ := os.Getwd()
				wd, _ = filepath.Abs(wd)
				opts.pkgName = filepath.Base(wd)
			}
			if !isValidPkgName(opts.pkgName) {
				fatalf("invalid package name: %s\n", opts.pkgName)
			}

			err := tryLoadTemplateFile(getDaoTemplate(opts))
			if err != nil {
				fatalf("load template file: %v\n", err)
			}
		},
		Run: func(cmd *cobra.Command, args []string) {
			for _, table := range args {
				database, table, _ := splitDatabaseTable(table)
				err := generateDao(database, table, opts)
				if err != nil {
					fatalf("generate dao: %v\n", err)
				}
			}
		},
		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
			database, table, prefix := splitDatabaseTable(toComplete)
			tbls, _ := showTables(database, table)
			for i, tbl := range tbls {
				tbls[i] = prefix + tbl
			}
			return tbls, cobra.ShellCompDirectiveNoFileComp
		},
	}
	opts.common.AddFlags(cmd)
	cmd.Flags().StringVarP(&opts.pkgName, "package", "p", "", "specify package name")
	cmd.Flags().BoolVar(&opts.dump, "dump", false, "dump document")
	cmd.Flags().StringVarP(&opts.template, "template", "t", "", "specify template name")
	cmd.Flags().StringVarP(&opts.output, "output", "o", "", "specify output name")
	cmd.Flags().StringArrayVarP(&opts.imports, "import", "I", nil, "add import")
	return cmd
}
